#!/usr/bin/python3.0

#
# OpenLookup Cartographer - olgraph.py
#
# Copyright 2009 Helsinki Institute for Information Technology
# and the authors.
#
# Authors: Toni Ruottu <toni.ruottu@iki.fi>
#

# Crawls the OpenLookup network and produces a graph picture in
# Graphviz's Graph File Language. You can use an application called
# "dot" from Graphviz to convert the output to some more popular
# format.

# Usage example:
#
# To create a picture of the network's current form (on Ubuntu),
# I'd first install "dot" with command...
#
# sudo apt-get install graphviz
#
# Then I'd create the picture (olmap.png) by commanding...
#
# ./olgraph.py | dot -T png > olmap.png
#

# Permission is hereby granted, free of charge, to any person
# obtaining a copy of this software and associated documentation files
# (the "Software"), to deal in the Software without restriction,
# including without limitation the rights to use, copy, modify, merge,
# publish, distribute, sublicense, and/or sell copies of the Software,
# and to permit persons to whom the Software is furnished to do so,
# subject to the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

import urllib.request, urllib.error
import json
import socket

def getconnections(node):
    url = 'http://%s:%d/connection_info' % node
    try:
        raw = urllib.request.urlopen(url,timeout=10).read().decode('utf-8')
    except:
        return None
    connections = json.loads(raw)
    fieldnames = connections.pop(0)
    return [dict(list(zip(fieldnames,c))) for c in connections]

def getnodeinfo(node):
    url = 'http://%s:%d/node_info' % node
    try:
        raw = urllib.request.urlopen(url,timeout=10).read().decode('utf-8')
    except:
        return None
    nodeinfo = json.loads(raw)
    return dict(nodeinfo)

def getxmlrpcstats(node):
    url = 'http://%s:%d/xmlrpc_stats' % node
    try:
        raw = urllib.request.urlopen(url,timeout=10).read().decode('utf-8')
    except:
        return None
    xmlrpcstats = json.loads(raw)
    return xmlrpcstats

def target(connection):
    return (connection['IP address'], connection['Access port'])

def crawl(seed):
    todo = list(seed)
    done = {}

    while len(todo) > 0:
        node = todo.pop()
        if node not in list(done.keys()):
            connections = getconnections(node)
            if connections:
                for c in connections:
                    yetanother = target(c)
                    todo.append(yetanother) 
            done[node] = connections
    return done
 
def defaultseed():
    host = 'any.openlookup.net'
    port = 5851

    seed = set()
    for i in range(10):
        ip = str(socket.gethostbyname(host))
        node = (ip, port)
        seed.add(node)
    return seed

def computearrows(network):
    arrows = set([])
    reverse = lambda x_y:(x_y[1],x_y[0])
    for node, connections in list(network.items()):
        if connections:
            for c in connections:
                arrow = (node, target(c))
                if c['Kind'] in ('Incoming', 'Accepting'):
                    arrow = reverse(arrow)
                arrows.add(arrow)
    return arrows
   
def dot(connections, nodes):

    arrows  = computearrows(connections)

    head    = ['digraph openlookup {']
    foot    = ['}']
    br      = ['']

    formats = []
    for id, formating in list(nodes.items()):
        format = ','.join(['%s="%s"' % i for i in list(formating.items())])
        formats.append('\t{node [%s] "%s";}' % (format, id))
    
    links   = ['\t"%s" -> "%s";' %  i for i in arrows]

    lines = head + br + formats + br + links + foot

    return '\n'.join(lines)

seed = defaultseed()
connections = crawl(seed)
nodes = list(connections.keys())
nodeinfo = list(zip(nodes, list(map(getnodeinfo, nodes))))

properties = {}
for node, info in nodeinfo:
    if info:
        lab = str(info['Name'])
        if info['Name'] == None:
           lab += ' ' + str(node)
        properties[node] = {'label':lab}
    else:
        properties[node] = {'label':str(node),'color':'gray','style':'filled'}

for s in seed:
    properties[s]['shape'] = 'box'

#print( nodeinfo )

print(dot(connections, properties))


